package com.appistry.maven.plugin.far.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * File handling utility class
 * 
 */
public class FileUtils {

	private static final Log logger = LogFactory.getLog(FileUtils.class);

	private static final String CVS = "CVS";
	private static final String SVN = ".svn";

	// for larger files (20Mb) use streams
	private static final long FILE_SIZE_LIMIT = 20971520l;
	private static final int STREAM_BUFFER_SIZE = 32768;
	private static final int CHANNEL_BUFFER_SIZE = 65536;

	/**
	 * Copy source directory recursively to destination
	 * 
	 * TODO: Should we provide an option "boolean recursive" which will allow
	 * users to set whether they want to copy directories recursively or not?
	 * 
	 * @param srcDir
	 * @param destDir
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public static void copyDirectory(final File srcDir, final File destDir)
			throws IOException, IllegalArgumentException {
		Assert.notNull(srcDir, "Source directory argument was null");
		Assert.notNull(destDir, "Destination directory argument was null");

		System.out.println("src: " + srcDir.getAbsolutePath() + ", dest: "
				+ destDir.getAbsolutePath());

		if (srcDir.isDirectory()) {
			if (!destDir.exists()) {
				if (!destDir.mkdirs()) {
					throw new IOException("Failed to create directory "
							+ destDir.getAbsolutePath());
				}
			}

			// TODO: make filters configurable
			String[] children = srcDir.list(new AndFileFilter(
					new NotFileFilter(new NameFileFilter(SVN)),
					new NotFileFilter(new NameFileFilter(CVS))));

			for (int i = 0; i < children.length; i++) {
				copyDirectory(new File(srcDir, children[i]), new File(destDir,
						children[i]));
			}
		} else {
			copyFile(srcDir, destDir);
		}
	}

	/**
	 * Copy specified source file to destination
	 * 
	 * @param src
	 * @param dest
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public static void copyFile(final File src, File dest) throws IOException,
			IllegalArgumentException {
		Assert.notNull(src, "Source file cannot be null");
		Assert.notNull(dest, "Destination file cannot be null");

		if (!src.isFile() || !src.exists()) {
			throw new IllegalArgumentException("Source file '"
					+ src.getAbsolutePath() + "' not found!");
		}

		// what if the user passed in the destination file name that had the
		// same name as a destination directory? File will get copied under the
		// destination directory
		if (dest.exists()) {
			if (dest.isDirectory()) {
				dest = new File(dest, src.getName());
			}
		}

		// create destination parent directories (since user might have passed
		// these as an absolute path)
		File destParent = dest.getParentFile();
		if (!destParent.exists())
			if (!destParent.mkdirs())
				throw new IOException("Failed to create directory '"
						+ destParent.getAbsolutePath() + "'");

		// copy file
		long fileSize = src.length();
		if (fileSize > FILE_SIZE_LIMIT) {
			FileInputStream fis = new FileInputStream(src);
			FileOutputStream fos = new FileOutputStream(dest);
			try {
				int doneCnt = -1, bufSize = STREAM_BUFFER_SIZE;
				byte buf[] = new byte[bufSize];
				while ((doneCnt = fis.read(buf, 0, bufSize)) >= 0)
					if (doneCnt == 0)
						Thread.yield();
					else
						fos.write(buf, 0, doneCnt);
				fos.flush();
			} finally {
				try {
					fis.close();
				} catch (IOException e) {
					logger.warn("Could not close inputstream", e);
				}
				try {
					fos.close();
				} catch (IOException e) {
					logger.warn("Could not close outputstream", e);
				}
			}
		} else { // smaller files, use channels
			FileInputStream fis = new FileInputStream(src);
			FileOutputStream fos = new FileOutputStream(dest);
			FileChannel fcin = fis.getChannel();
			FileChannel fcout = fos.getChannel();
			try {
				long offs = 0, doneCnt = 0, copyCnt = Math.min(
						CHANNEL_BUFFER_SIZE, fileSize);
				do {
					doneCnt = fcin.transferTo(offs, copyCnt, fcout);
					offs += doneCnt;
					fileSize -= doneCnt;
				} while (fileSize > 0);
			} finally { // cleanup
				try {
					fcin.close();
				} catch (IOException e) {
					logger.warn("Could not close filechannel", e);
				}
				try {
					fcout.close();
				} catch (IOException e) {
					logger.warn("Could not close filechannel", e);
				}
				try {
					fis.close();
				} catch (IOException e) {
					logger.warn("Could not close inputstream", e);
				}
				try {
					fos.close();
				} catch (IOException e) {
					logger.warn("Could not close outputstream", e);
				}
			}
		}
	}

	// private static FilenameFilter filter(final String regex) {
	// return new FilenameFilter() {
	// private Pattern pattern = Pattern.compile(regex);
	//
	// public boolean accept(File dir, String name) {
	// return !pattern.matcher(new File(name).getName()).matches();
	// }
	// };
	// }
	//
	// static class CVSFilter implements FilenameFilter {
	//
	// public boolean accept(File dir, String name) {
	// return !"CVS".equalsIgnoreCase(name);
	// }
	//
	// }
	//
	// static class SVNFilter implements FilenameFilter {
	//
	// public boolean accept(File dir, String name) {
	// return !".svn".equalsIgnoreCase(name);
	// }
	//
	// }

}